#ifndef SWEGL_PLANE_H
#define SWEGL_PLANE_H

namespace swegl
{

// a plane is defined by the equation: a * x + b * y + c * z + d = 0
// => the normal of the plane is n(a, b, c)
// => distance along the normal to go back to the origin is 'd'
class Plane
{
public:
	// default contructor, do not initialize components
	Plane();
	Plane(float a, float b, float c, float d);
	Plane(const Vec3 &normal, float distance);

	Plane(const Plane &rhs);

	Plane &set(float a, float b, float c, float d);
	Plane &operator = (const Plane &rhs);
	Plane operator - () const;
	
	float operator [] (uint index) const;
	float &operator [] (uint index);

	bool operator == (const Plane &rhs) const;
	bool operator != (const Plane &rhs) const;
	
	// return the normal of the plane
	const Vec3 &normal() const;
	Vec3 &normal();
	// normalize the plane normal, and adjust 'd'. To only normalize the normal, call normal().normalize()
	void normalize();
	// set d = -dist
	void setDistance(float dist);
	// return the distance (-d)
	float distance() const;
	
	// return the distance between v and the plane.
	// > 0 value if v is on the positive side of the place, < 0 if on the negative side, 0 if v is on the plane
	// rmq: the absolute value of the distance is the 'true' distance only if the normal plane is normalized
	float distance(const Vec3 &v) const;
	
	enum Side
	{
		SidePositive,
		SideNegative,
		SideOn,
		SideCross
	};
	// return on which side the point v lies on
	Side side(const Vec3 &v, float epsilon = 0.0f) const;
	// return on which side the box lies on
	Side side(const AABox &box) const;
	// return on which side the box (defined by center and halfSize) lies on
	Side side(const Vec3 &center, const Vec3 &halfSize) const;
	
	const Vec4 &toVec4() const;
	Vec4 &toVec4();
	const float *toFloatPtr() const;
	float *toFloatPtr();
	
private:
	float a;
	float b;
	float c;
	float d;
};

inline Plane::Plane()
{
}

inline Plane::Plane(float a, float b, float c, float d) : a(a), b(b), c(c), d(d)
{
}

inline Plane::Plane(const Vec3 &normal, float distance) : a(normal.x), b(normal.y), c(normal.z), d(-distance)
{
}

inline Plane::Plane(const Plane &rhs) : a(rhs.a), b(rhs.b), c(rhs.c), d(rhs.d)
{
}

inline Plane &Plane::set(float a, float b, float c, float d)
{
	this->a = a;
	this->b = b;
	this->c = c;
	this->d = d;
	return *this;
}

inline Plane &Plane::operator = (const Plane &rhs)
{
	this->a = rhs.a;
	this->b = rhs.b;
	this->c = rhs.c;
	this->d = rhs.d;
	return *this;
}

inline Plane Plane::operator - () const
{
	return Plane(-a, -b, -c, -d);
}

inline float Plane::operator [] (uint index) const
{
	return (&a)[index];
}
	
inline float &Plane::operator [] (uint index)
{
	return (&a)[index];
}

inline bool Plane::operator == (const Plane &rhs) const
{
	return this->a == rhs.a && this->b == rhs.b && this->c == rhs.c && this->d == rhs.d;
}

inline bool Plane::operator != (const Plane &rhs) const
{
	return this->a != rhs.a || this->b != rhs.b || this->c != rhs.c || this->d != rhs.d;
}

inline const Vec3 &Plane::normal() const
{
	return *reinterpret_cast<const Vec3 *>(&a);
}

inline Vec3 &Plane::normal()
{
	return *reinterpret_cast<Vec3 *>(&a);
}

inline void Plane::normalize()
{
	float length = normal().normalize();
	d /= length;
}

inline void Plane::setDistance(float dist)
{
	d = -dist;
}

inline float Plane::distance() const
{
	return -d;
}

inline float Plane::distance(const Vec3 &v) const
{
	return a * v.x + b * v.y + c * v.z + d;
}

inline Plane::Side Plane::side(const Vec3 &v, float epsilon) const
{
	float dist = distance(v);
	if (dist > epsilon)
		return SidePositive;
	else if (dist < -epsilon)
		return SideNegative;
	else
		return SideOn;
}

inline Plane::Side Plane::side(const AABox &box) const
{
	return side(box.center(), box.halfSize());
}

inline Plane::Side Plane::side(const Vec3 &center, const Vec3 &halfSize) const
{
	float centerDistance = distance(center);
	//normal.absDotProduct(halfSize);
	float maxAbsoluteDistance = Abs(a * halfSize.x) + Abs(b * halfSize.y) + Abs(c * halfSize.z);
	if (centerDistance > maxAbsoluteDistance)
		return SidePositive;
	else if (centerDistance < - maxAbsoluteDistance)
		return SideNegative;
	else
		return SideCross;
}

inline const Vec4 &Plane::toVec4() const
{
	return *reinterpret_cast<const Vec4 *>(&a);
}

inline Vec4 &Plane::toVec4()
{
	return *reinterpret_cast<Vec4 *>(&a);
}

inline const float *Plane::toFloatPtr() const
{
	return &a;
}

inline float *Plane::toFloatPtr()
{
	return &a;
}

}

#endif
